package com.toplabs.unashu.aspect;

import cn.hutool.core.util.ReflectUtil;
import com.toplabs.unashu.annotation.BackoffProcess;
import com.toplabs.unashu.annotation.Recover;
import com.toplabs.unashu.annotation.RetryableProcess;
import com.toplabs.unashu.exception.RetryableException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author toplabs
 * @since 2022/11/22
 * <p>This java file was created by toplabs in 2022/11/22.
 * The following is the description information about this file:</p>
 * <p>description:自定义重试过程切面</p>
 */
@Slf4j
@Aspect
@Component
public class RetryProcessAspect {

    @Pointcut("@annotation(com.toplabs.unashu.annotation.RetryableProcess)")
    public void pointCutRetry() {
    }

    /**
     * 埋点拦截器具体实现
     *
     * @param joinPoint 连接点
     * @return {@link Object}
     * @throws Throwable throwable
     */
    @Around("pointCutRetry()")
    public Object methodRetryHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RetryableProcess process = method.getAnnotation(RetryableProcess.class);
        int maxAttempts = process.maxAttempts();
        BackoffProcess backoff = process.backoff();
        long sleepSecond = backoff.value();
        long maxDelay = backoff.maxDelay();
        double multiplier = backoff.multiplier();
        if (multiplier <= 0) multiplier = 1;
        int retryCount = 0;
        RetryableException ex;
        do {
            try {
                return joinPoint.proceed();
            } catch (RetryableException e) {
                // 还需要进行尝试
                if (retryCount < maxAttempts) {
                    // 时间间隔达到限定
                    if (sleepSecond > maxDelay) {
                        sleepSecond = maxDelay;
                        log.debug("等待时间太长，更新为{}毫秒", maxDelay);
                    }
                    log.debug("等待{}毫秒后进行第{}次尝试", sleepSecond, retryCount + 1);
                }
                Thread.sleep(sleepSecond);
                sleepSecond *= multiplier;
                ex = e;
            }
        } while (++retryCount <= maxAttempts);
        // 重试失败
        try {
            Object[] args = joinPoint.getArgs();
            Object[] argsCopy = new Object[args.length + 1];
            Class<?>[] argsType = new Class[args.length + 1];
            for (int i = 0; i < args.length; i++) {
                argsCopy[i] = args[i];
                argsType[i] = args[i].getClass();
            }
            argsCopy[args.length] = ex;
            argsType[args.length] = RetryableException.class;
            Class<?> aClass = joinPoint.getTarget().getClass();
            String recoverName = process.recover();
            Method recover = aClass.getDeclaredMethod(recoverName, argsType);
            ReflectUtil.setAccessible(recover);
            Recover annotation = recover.getAnnotation(Recover.class);
            // 判断是否为Recover标记的方法
            if (annotation == null) {
                throw ex;
            }
            return recover.invoke(joinPoint.getTarget(), argsCopy);
        } catch (NoSuchMethodException e) {
            // 未找到方法,则不执行,抛出之前的异常
            throw ex;
        }
    }
}